//------------------------------------------------------------------------------
//  memorystream.cc
//  (C) 2006 Radon Labs GmbH
//------------------------------------------------------------------------------
#include "Pch.h"
#include "AllocManager.h"
#include "memorystream.h"

namespace GBase
{

	//using namespace Math;

	//------------------------------------------------------------------------------
	/**
	*/
	MemoryStream::MemoryStream() :
		capacity(0),
		size(0),
		position(0),
		buffer(0)
	{
		// empty
	}

	//------------------------------------------------------------------------------
	/**
	*/
	MemoryStream::~MemoryStream()
	{
		// close the stream if still open
		if (this->IsOpen())
		{
			this->Close();
		}

		// release memory buffer if allocated
		if (0 != this->buffer)
		{
			//Memory::Free(Memory::StreamDataHeap, this->buffer);
			//appFree(this->buffer);
			G_FREE(this->buffer);
			this->buffer = 0;
		}
	}

	//------------------------------------------------------------------------------
	/**
	*/
	bool
	MemoryStream::CanRead() const
	{
		return true;
	}

	//------------------------------------------------------------------------------
	/**
	*/
	bool
	MemoryStream::CanWrite() const
	{
		return true;
	}

	//------------------------------------------------------------------------------
	/**
	*/
	bool
	MemoryStream::CanSeek() const
	{
		return true;
	}

	//------------------------------------------------------------------------------
	/**
	*/
	bool	MemoryStream::CanBeMapped() const
	{
		return true;
	}

	//------------------------------------------------------------------------------
	/**
	*/
	void	MemoryStream::SetSize(Size s)
	{
		checkSlow(!this->IsOpen());
		if (s > this->capacity)
		{
			this->Realloc(s);
		}
		this->size = s;
	}

	//------------------------------------------------------------------------------
	/**
	*/
	Stream::Size
	MemoryStream::GetSize() const
	{
		return this->size;
	}

	//------------------------------------------------------------------------------
	/**
	*/
	Stream::Position
	MemoryStream::GetPosition() const
	{
		return this->position;
	}

	//------------------------------------------------------------------------------
	/**
		Open the stream for reading or writing. The stream may already contain
		data if it has been opened/closed before. 
	*/
	bool
	MemoryStream::Open()
	{
		checkSlow(!this->IsOpen());
	    
		// nothing to do here, allocation happens in the first Write() call
		// if necessary, all we do is reset the read/write position to the
		// beginning of the stream
		if (Stream::Open())
		{
			this->position = 0;
			return true;
		}
		return false;
	}

	//------------------------------------------------------------------------------
	/**
		Close the stream. The contents of the stream will remain intact until
		destruction of the object, so that the same data may be accessed 
		or modified during a later session. 
	*/
	void
	MemoryStream::Close()
	{
		checkSlow(this->IsOpen());
		if (this->IsMapped())
		{
			this->Unmap();
		}
		Stream::Close();
	}

	//------------------------------------------------------------------------------
	/**
	*/
	void
	MemoryStream::Write(const void* ptr, Size numBytes)
	{
		checkSlow(this->IsOpen());
		checkSlow(!this->IsMapped()); 
		checkSlow((WriteAccess == this->accessMode) || (AppendAccess == this->accessMode) || (ReadWriteAccess == this->accessMode));
		checkSlow((this->position >= 0) && (this->position <= this->size));

		// if not enough room, allocate more memory
		if (!this->HasRoom(numBytes))
		{
			this->MakeRoom(numBytes);
		}

		// write data to stream
		checkSlow((this->position + numBytes) <= this->capacity);
		//Memory::Copy(ptr, this->buffer + this->position, numBytes);
		::memcpy(this->buffer + this->position, ptr, numBytes);
		this->position += numBytes;
		if (this->position > this->size)
		{
			this->size = this->position;
		}
	}

	//------------------------------------------------------------------------------
	/**
	*/
	Stream::Size
	MemoryStream::Read(void* ptr, Size numBytes)
	{
		checkSlow(this->IsOpen());
		checkSlow(!this->IsMapped()); 
		checkSlow((ReadAccess == this->accessMode) || (ReadWriteAccess == this->accessMode));
		checkSlow((this->position >= 0) && (this->position <= this->size));

		// check if end-of-stream is near
		//Size readBytes = n_min(numBytes, this->size - this->position);
		Size readBytes = __MIN(numBytes, this->size - this->position);
		checkSlow((this->position + readBytes) <= this->size);
		if (readBytes > 0)
		{
			//Memory::Copy(this->buffer + this->position, ptr, readBytes);
			::memcpy(ptr, this->buffer + this->position, readBytes);
			this->position += readBytes;
		}
		return readBytes;
	}

	void MemoryStream::Dump()
	{
		unsigned char* temp_buf = this->buffer + position;
		unsigned int num = this->size - this->position;
		printf("<%d>: ", num);
		for (unsigned int i = 0; i < num; ++i)
		{
			printf("%02x ", *(temp_buf));
			++temp_buf;
		}
		printf("\r\n");
	}

	//------------------------------------------------------------------------------
	/**
	*/
	void
	MemoryStream::Seek(Offset offset, SeekOrigin origin)
	{
		checkSlow(this->IsOpen());
		checkSlow(!this->IsMapped()); 
		checkSlow((this->position >= 0) && (this->position <= this->size));
		switch (origin)
		{
			case Begin:
				this->position = offset;
				break;
			case Current:
				this->position += offset;
				break;
			case End:
				this->position = this->size + offset;
				break;
		}

		// make sure read/write position doesn't become invalid
		//this->position = Math::n_iclamp(this->position, 0, this->size);
		__CLAMP(this->position, 0, this->size);
	}

	//------------------------------------------------------------------------------
	/**
	*/
	bool
	MemoryStream::Eof() const
	{
		checkSlow(this->IsOpen());
		checkSlow(!this->IsMapped());
		checkSlow((this->position >= 0) && (this->position <= this->size));
		return (this->position == this->size);
	}

	//------------------------------------------------------------------------------
	/**
	*/
	bool
	MemoryStream::HasRoom(Size numBytes) const
	{
		return ((this->position + numBytes) <= this->capacity);
	}

	//------------------------------------------------------------------------------
	/**
		This (re-)allocates the memory buffer to a new size. If the new size
		is smaller then the existing size, the buffer contents will be clipped.
	*/
	void	MemoryStream::Realloc(Size newCapacity)
	{
		//unsigned char* newBuffer = (unsigned char*) Memory::Alloc(Memory::StreamDataHeap, newCapacity);
		unsigned char* newBuffer = (unsigned char*) G_MALLOC(newCapacity);
		checkSlow(0 != newBuffer);
		//int newSize = n_min(newCapacity, this->size);
		int newSize = __MIN(newCapacity, this->size);
		if (0 != this->buffer)
		{
			//Memory::Copy(this->buffer, newBuffer, newSize);
			::memcpy(newBuffer, this->buffer, newSize);
			//Memory::Free(Memory::StreamDataHeap, this->buffer);
			G_FREE(this->buffer);
		}
		this->buffer = newBuffer;
		this->size = newSize;
		this->capacity = newCapacity;
		if (this->position > this->size)
		{
			this->position = this->size;
		}
	}

	//------------------------------------------------------------------------------
	/**
		This method makes room for at least N more bytes. The actually allocated
		memory buffer will be greater then that. This operation involves a copy
		of existing data.
	*/
	void
	MemoryStream::MakeRoom(Size numBytes)
	{
		checkSlow(numBytes > 0);
		checkSlow((this->size + numBytes) > this->capacity);

		// compute new capacity
		//Size newCapacity = n_max(16, n_max(2 * this->capacity, this->size + numBytes));
		Size newCapacity = __MAX(16, __MAX(2 * this->capacity, this->size + numBytes));
		checkSlow(newCapacity > this->capacity);

		// (re-)allocate memory buffer
		this->Realloc(newCapacity);
	}

	//------------------------------------------------------------------------------
	/**
		Map the stream for direct memory access. This is much faster then 
		reading/writing, but less flexible. A mapped stream cannot grow, instead
		the allowed memory range is determined by GetSize(). The read/writer must 
		take special care to not read or write past the memory buffer boundaries!
	*/
	void*
	MemoryStream::Map()
	{
		checkSlow(this->IsOpen());
		Stream::Map();
		checkSlow(this->GetSize() > 0);
		return this->buffer;
	}

	//------------------------------------------------------------------------------
	/**
		Unmap a memory-mapped stream.
	*/
	void
	MemoryStream::Unmap()
	{
		checkSlow(this->IsOpen());
		Stream::Unmap();
	}

	//------------------------------------------------------------------------------
	/**
		Get a direct pointer to the raw data. This is a convenience method
		and only works for memory streams.
		NOTE: writing new data to the stream may/will result in an invalid
		pointer, don't keep the returned pointer around between writes!
	*/
	void*
	MemoryStream::GetRawPointer() const
	{
		checkSlow(0 != this->buffer);
		return this->buffer;
	}

	void MemoryStream::InitRawPointer(unsigned char* buffer_, Size size_)
	{
		//if (this->buffer)
		//{
		//	MemoryFree(this->buffer);
		//}
		this->buffer = buffer_;
		this->size = size_;
		this->position = 0;
		this->capacity = this->size;
		if (!this->IsOpen())
		{
			this->Open();
		}
	}
} // namespace Core
